#include <linux/netlink.h>
#include <linux/version.h>
#include <net/sock.h>
#include <linux/skbuff.h>
#include <net/net_namespace.h>

#include "socs/comm/types.h"
#include "socs/comm/socs.h"
#include "socs/comm/constants.h"
#include "socs/comm/config.h"
#include "socs/mod/netlink.h"
#include "socs/mod/sap.h"
#include "socs/mod/neigh.h"
#include "socs/mod/multicast.h"
#include "socs/mod/pool6.h"

struct sock *nl_sk = NULL;
static DEFINE_MUTEX(nl_mtx);
static char *saIP_status_str[4] = {"none", "grabbing", "abandoning", "serving"};


static int  nl_send_to_user(int pid, char *data, int data_len)
{
	struct sk_buff *skb = NULL;
	struct nlmsghdr *nl_hdr = NULL;
	int res;

	if (pid <= 0) {
		log_err(ERR_PID, "the pid  %d is not available", pid);
		return -1;
	}

	skb = nlmsg_new(NLMSG_ALIGN(data_len), GFP_ATOMIC);

	if (!skb) {
		log_err(ERR_ALLOC_FAILED, "%s  allocate a response skb to the user failed.",
				__func__);
		return -1;
	}

	nl_hdr = nlmsg_put(skb,
					   0, /* src_pid (0 = kernel) */
					   0, /* seq */
					   0, /* type */
					   data_len, /* payload len */
					   NLM_F_MULTI); /* flags. */
	NETLINK_CB(skb).dst_group = 0; /* not in mcast group */
	NETLINK_CB(skb).portid = 0;      /* from kernel */
	memcpy(nlmsg_data(nl_hdr), data, data_len);
	res = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);

	if (res < 0) {
		log_err(ERR_NLSEND_FAILED, "%d while returning response to the user.", res);
		return -1;
	}

	log_debug("nl_send_skb finish");
	return 0;
}


static int nl_rcv_msg(struct sk_buff *skb)
{
	char temp[50];
	struct socs_neigh_node *node = NULL;
	struct localnode *lcn = NULL;
	struct list_head *head = NULL;
	struct multicast_info *mul_info = NULL;
	struct nlmsghdr *nlh = NULL;
	struct arguments *args = NULL;
	//char buf[NLMSG_DEFAULT_SIZE]={};
	//char buf[MAX_PAYLOAD*2]= {};
	struct socs_config_t config;
	struct sap_table *saptb4 = NULL;
	struct sap_table *saptb6 = NULL;
	int i = 0;
	int buflen = MAX_PAYLOAD * 8;
	char *buf = kmalloc(buflen, GFP_ATOMIC);
	struct sap *sap;

	if (buf == NULL) {
		nl_send_to_user(nlh->nlmsg_pid, "Kernel memory is not enough!\n",
						strlen("Kernel memory is not enough!\n"));
		return 0;
	}

	memset(buf, 0, buflen);
	nlh = nlmsg_hdr(skb);
	log_debug("nl_rcv_msg ,pid:%d", nlh->nlmsg_pid);
	args = (struct arguments *)NLMSG_DATA(nlh);

	switch (args->key) {
	case ARGP_SHOW:
		log_debug("you send the s ");
		socs_config_get(ARGP_CSDOMAIN, config.csDomain);
		socs_config_get(ARGP_HOSTNAME, config.hostname);
		socs_config_get(ARGP_TMHELLO, &config.tmHello);
		socs_config_get(ARGP_TMDEAD, &config.tmDead);
		socs_config_get(ARGP_TMSTABLE, &config.tmStable);
		socs_config_get(ARGP_MCIP4, &config.mcIP4);
		socs_config_get(ARGP_MCIP6, &config.mcIP6);
		mul_info = socs_mul_get();
		sprintf(buf,
				"%-10s%s\n%-10s%s\n%-10s%u msec\n%-10s%u msec\n%-10s%u msec\n%-10s%pI4\n%-10s%pI6c\n",
				"csDomain:", config.csDomain,
				"hostname:", config.hostname,
				"tmHello:", config.tmHello,
				"tmDead:", config.tmDead,
				"tmStable:", config.tmStable,
				"mcIP4:", &config.mcIP4,
				"mcIP6:", &config.mcIP6);

		if (mul_info != NULL) {
			sprintf(buf + strlen(buf), "mulicast dev name:%s",
					mul_info->ifname);

			if (mul_info->family == IPV4) {
				sprintf(buf + strlen(buf), " (IPv4 %pI4c)\n", &mul_info->local_addr.addr4);
			} else if (mul_info->family == IPV6) {
				sprintf(buf + strlen(buf), " (IPv6 %pI6c)\n", &mul_info->local_addr.addr6);
			} else {
				sprintf(buf + strlen(buf), "\n");
			}
		}

		nl_send_to_user(nlh->nlmsg_pid, buf, strlen(buf));
		break;

	case ARGP_SAIP:
		log_debug("you send saIP");
		sprintf(buf, "%s\n%-20s%-20s%-15s%-15s\n",
				"***************************************************************",
				"saIP4(vIP)", "SAP", "is_locallink", "status");
		saptb_read_lock(IPV4);
		saptb4 = saptb_get(IPV4);
		sap = saptb_iterator(saptb4, NULL);

		while (sap) {
			sprintf(buf + strlen(buf), "%-20pI4%-20pI4%-15d%-15s\n", &sap->saIP,
					&sap->dIP,
					sap->is_locallink, saIP_status_str[sap->status]);
			sap = saptb_iterator(NULL, sap);
		}

		/* for (i = 0; i < saptb4->sap_num; i++) {                                             */
		/*     sprintf(buf + strlen(buf), "%-20pI4%-20pI4%-15d%-15s\n", &saptb4->saps[i].saIP, */
		/*             &saptb4->saps[i].dIP,                                                   */
		/*             saptb4->saps[i].is_locallink, saIP_status_str[saptb4->saps[i].status]); */
		/* }                                                                                   */
		saptb_read_unlock(IPV4);
		// IPv6 addresses are too long so that sometimes the output is awful
		// modified by mayc@reyzar.com, 2016-04-28
		sprintf(buf + strlen(buf), "%s\n%-40s%-40s%-15s%-15s\n",
				"*******************************************************************************************************",
				"saIP6(vIP)", "SAP", "is_locallink", "status");
		saptb_read_lock(IPV6);
		saptb6 = saptb_get(IPV6);
		sap = saptb_iterator(saptb6, NULL);

		while (sap) {
			if (sap->prefix < 128) {
				sprintf(temp, "%pI6c/%d", &sap->saIP, sap->prefix);
			} else {
				sprintf(temp, "%pI6c", &sap->saIP);
			}

			sprintf(buf + strlen(buf), "%-40s%-40pI6c%-15d%-15s\n", temp,
					&sap->dIP,
					sap->is_locallink, saIP_status_str[sap->status]);
			sap = saptb_iterator(NULL, sap);
		}

		/* for (i = 0; i < saptb6->sap_num; i++) {                                             */
		/*     if (saptb6->saps[i].prefix < 128) {                                             */
		/*         sprintf(temp, "%pI6c/%d", &saptb6->saps[i].saIP, saptb6->saps[i].prefix);   */
		/*     } else {                                                                        */
		/*         sprintf(temp, "%pI6c", &saptb6->saps[i].saIP);                              */
		/*     }                                                                               */
		/*     sprintf(buf + strlen(buf), "%-40s%-40pI6c%-15d%-15s\n", temp,                   */
		/*             &saptb6->saps[i].dIP,                                                   */
		/*             saptb6->saps[i].is_locallink, saIP_status_str[saptb6->saps[i].status]); */
		/* }                                                                                   */
		saptb_read_unlock(IPV6);
		nl_send_to_user(nlh->nlmsg_pid, buf, strlen(buf));
		break;

//beigin add by Leo.huang 2015-07-23
	case ARGP_NEIGH:
		log_debug("you send neigh");
		socs_neigh_read_lock(IPV4);
		head = socs_neigh_get(IPV4);

		if (list_empty(head)) {
			snprintf(buf + strlen(buf), buflen - strlen(buf),
					 "The socs_neigh 4 is empty\n");
		} else {
			list_for_each_entry(node, head, list_hook) {
				lcn = &node->lcn;
				snprintf(buf + strlen(buf), buflen - strlen(buf),
						 "\n%s\nhostname:%7s\tfamily:%s\ttotalsapcnt:%u\tlcl_num:%u\n",
						 "******************************************************************",
						 lcn->hostname, "IPV4", lcn->totalsapcnt, lcn->lcl_num);
				snprintf(buf + strlen(buf), buflen - strlen(buf),
						 "name\taddress\t\t\tsapcnt\ttotalrq(forward to this host num)\n");
				node_for_each_link(i, lcn) {
					snprintf(buf + strlen(buf), buflen - strlen(buf), "%s\t%pI4c\t\t%u\t%u\n",
							 lcn->lcls[i].name, &lcn->lcls[i].address.ipv4,
							 lcn->lcls[i].sapcnt, atomic_read(&lcn->lcls[i].totalrq));
				}
			}
		}

		socs_neigh_read_unlock(IPV4);
		socs_neigh_read_lock(IPV6);
		head = socs_neigh_get(IPV6);

		if (list_empty(head)) {
			snprintf(buf + strlen(buf), buflen - strlen(buf),
					 "The socs_neigh 6 is empty\n");
		} else {
			list_for_each_entry(node, head, list_hook) {
				lcn = &node->lcn;
				// IPv6 addresses are too long so that sometimes the output is awful
				// modified by mayc@reyzar.com, 2016-04-28
				snprintf(buf + strlen(buf), buflen - strlen(buf),
						 "\n%s\nhostname:%7s\tfamily:%s\ttotalsapcnt:%u\tlcl_num:%u\n",
						 "********************************************************************************",
						 lcn->hostname, "IPV6", lcn->totalsapcnt, lcn->lcl_num);
				snprintf(buf + strlen(buf), buflen - strlen(buf),
						 "name\taddress\t\t\t\t\tsapcnt\ttotalrq(forward to this host num)\n");
				node_for_each_link(i, lcn) {
					snprintf(buf + strlen(buf), buflen - strlen(buf), "%s\t%-40pI6c%u\t%u\n",
							 lcn->lcls[i].name, &lcn->lcls[i].address.ipv6,
							 lcn->lcls[i].sapcnt, atomic_read(&lcn->lcls[i].totalrq));
				}
			}
		}
		socs_neigh_read_unlock(IPV6);
		nl_send_to_user(nlh->nlmsg_pid, buf, strlen(buf));
		break;
	case ARGP_POOL6:
		log_debug("you send the pool6 ");
		pool6_operat(args, buf);
		nl_send_to_user(nlh->nlmsg_pid, buf, strlen(buf));
		break;
	default:
		sprintf(buf, "Command seems wrong; --help or --usage for info.\n");
		nl_send_to_user(nlh->nlmsg_pid, buf, strlen(buf));
	}

	kfree(buf);
	return 0;
}
static void nl_rcv(struct sk_buff *skb)
{
	mutex_lock(&nl_mtx);
	nl_rcv_msg(skb);
	mutex_unlock(&nl_mtx);
}

int  nl_kernel_init(void)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
	nl_sk = netlink_kernel_create(&init_net, SOCS_NETLINK, 0, nl_rcv,
								  NULL, THIS_MODULE);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0)
	struct netlink_kernel_cfg nl_cfg = { .input  = nl_rcv };
	nl_sk = netlink_kernel_create(&init_net, SOCS_NETLINK, THIS_MODULE, &nl_cfg);
#else
	struct netlink_kernel_cfg nl_cfg = { .input  = nl_rcv };
	nl_sk = netlink_kernel_create(&init_net, SOCS_NETLINK, &nl_cfg);
#endif

	if (!nl_sk) {
		log_err(ERR_SOCKET, "%s creation of netlink socket failed.", __func__);
		return -EINVAL;
	}

	log_debug("Netlink socket created");
	return 0;
}
void   nl_kernel_destroy(void)
{
	netlink_kernel_release(nl_sk);
}
